package cn.zifangsky.hashtable.slidingtime;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 基于“滑动时间窗口算法”的缓存队列实现
 *
 * @author zifangsky
 * @date 2020/7/1
 * @since 1.0.0
 */
public class SlidingTimeCache<V> {
    /**
     * 默认的缓存大小
     */
    public static final int DEFAULT_INITIAL_CAPACITY = 128;

    /**
     * 单个节点信息
     */
    public static class Node {
        /**
         * key
         */
        final String key;

        /**
         * 过期时间戳
         */
        long expired;

        /**
         * 下一个节点
         */
        Node next;

        public Node(String key) {
            this(key, -1);
        }

        public Node(String key, long expired) {
            this.key = key;
            this.expired = expired;
        }

        @Override
        public final String toString() {
            return String.format("key: %s, expired: %d", key, expired);
        }

        @Override
        public final boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Node node = (Node) o;
            return this.equals(key, node.key);
        }

        /**
         * 取 key 的 hashCode
         */
        @Override
        public final int hashCode() {
            return this.hashCode(key);
        }

        public int hashCode(Object o) {
            return o != null ? o.hashCode() : 0;
        }

        public boolean equals(Object a, Object b) {
            return Objects.equals(a, b);
        }
    }

    /**
     * 定义一个存储数据的链表
     */
    public static class LinkedNodeList {
        /**
         * 头节点
         */
        Node head;
        /**
         * 尾节点
         */
        Node tail;

        public LinkedNodeList() {
            this.head = null;
            this.tail = null;
        }

        /**
         * 判断是否存在指定节点
         * @param key key
         * @return boolean
         */
        public boolean contains(String key){
            if(key == null){
                return false;
            }

            Node temp = this.head;
            while (temp != null){
                if(key.equals(temp.key)){
                    return true;
                }

                temp = temp.next;
            }

            return false;
        }

        /**
         * 新增节点
         * @param newNode 新节点
         */
        public void add(Node newNode){
            if(newNode == null){
                return;
            }

            if(this.tail == null){
                this.tail = newNode;
                this.head = newNode;
            }else{
                this.tail.next = newNode;
                this.tail = newNode;
            }
        }

        /**
         * 移除头结点
         */
        public void removeHead(){
            if(this.head == null){
                return;
            }

            if(this.head == this.tail){
                this.head = null;
                this.tail = null;
            }else{
                this.head = this.head.next;
            }
        }

        /**
         * 移除尾结点
         */
        public void removeTail(){
            if(this.tail == null){
                return;
            }

            if(this.head == this.tail){
                this.head = null;
                this.tail = null;
            }else{
                Node temp = this.head;
                while (temp.next != this.tail){
                    temp = temp.next;
                }

                this.tail = temp;
                this.tail.next = null;
            }
        }

        /**
         * 移除指定节点
         */
        public void remove(Node node){
            if(node == this.head){
                this.removeHead();
            }else if(node == this.tail){
                this.removeTail();
            }else{
                Node temp = this.head;
                while (temp.next != node){
                    temp = temp.next;
                }

                temp.next = node.next;
            }
        }

        /**
         * 移除某个结点
         */
        public void remove(String key){
            if(key == null){
                return;
            }

            Node temp = this.head;
            while (temp != null){
                if(key.equals(temp.key)){
                    this.remove(temp);
                    break;
                }else {
                    temp = temp.next;
                }
            }
        }

        /**
         * 移除过期节点
         */
        public LinkedList<String> removeExpiredNodes(){
            //所有已经过期的KEY
            LinkedList<String> expiredKeyList = new LinkedList<>();
            //当前精确到毫秒的时间戳
            long current = System.currentTimeMillis();

            Node temp = this.head;
            while (temp != null){
                if(temp.expired > current){
                    break;
                }

                //如果当前节点已经过期，则移除，并继续检查下一个节点
                expiredKeyList.add(temp.key);

                if(temp == this.tail) {
                    this.head = null;
                    this.tail = null;
                    break;
                }else {
                    temp = temp.next;
                    this.head =temp;
                }
            }

            return expiredKeyList;
        }
    }

    /* ---------------- Fields -------------- */

    /**
     * 存储KEY的链表
     */
    private LinkedNodeList nodeList;

    /**
     * 实际存储数据的Map
     */
    private Map<String, List<V>> keyDataMap;

    public SlidingTimeCache() {
        this.nodeList = new LinkedNodeList();
        this.keyDataMap = new HashMap<>(DEFAULT_INITIAL_CAPACITY);
    }

    /**
     * 通过KEY取值
     * @param key KEY
     * @param remove 是否获取后从CACHE移除
     * @return V
     */
    public synchronized List<V> get(String key, boolean remove){
        if(key == null){
            return null;
        }

        List<V> list = this.keyDataMap.get(key);
        if(list != null && !list.isEmpty() && remove){
            this.remove(key);
        }

        return list;
    }

    /**
     * Add操作，并设置过期时间
     * @param key KEY
     * @param seconds X秒后过期
     */
    public synchronized void add(String key, int seconds){
        this.add(key, null, seconds);
    }

    /**
     * Add操作，并设置过期时间
     * @param key KEY
     * @param value VALUE
     * @param seconds X秒后过期
     */
    public synchronized void add(String key, V value, int seconds){
        this.add(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * Add操作，并设置过期时间
     * @param key KEY
     * @param value VALUE
     * @param time 过期时间
     * @param unit 过期单位
     */
    public synchronized void add(String key, V value, int time, TimeUnit unit){
        if(key == null || unit == null || time < 0){
            throw new IllegalArgumentException("参数存在异常！");
        }

        //1. 判断是否已经存在指定KEY
        boolean exists = this.nodeList.contains(key);
        if (!exists) {
            //1.1 计算过期时间戳，并创建新节点
            long expired = System.currentTimeMillis() + unit.toMillis(time);
            Node newNode = new Node(key, expired);

            //1.2 新增
            this.nodeList.add(newNode);
        }

        //2. 将数据添加到Map
        this.addToDataMap(key, value);
    }

    /**
     * Add操作，并设置过期时间
     * @param keyValuePairList 一批数据
     * @param time 过期时间
     * @param unit 过期单位
     */
    public synchronized void addBatch(List<Pair<String, V>> keyValuePairList, int time, TimeUnit unit){
        if (keyValuePairList == null || keyValuePairList.isEmpty()) {
            return;
        }

        for(Pair<String, V> keyValuePair : keyValuePairList){
            this.add(keyValuePair.getLeft(), keyValuePair.getRight(), time, unit);
        }
    }

    /**
     * 移除指定的KEY
     * @param key KEY
     */
    public synchronized void remove(String key){
        if(key == null){
            throw new IllegalArgumentException("参数存在异常！");
        }

        this.keyDataMap.remove(key);
        this.nodeList.remove(key);
    }

    /**
     * 获取过期数据
     */
    public synchronized Map<String, List<V>> getExpiredData(){
        LinkedList<String> expiredKeyList = this.nodeList.removeExpiredNodes();

        Map<String, List<V>> expiredMap = new HashMap<>(expiredKeyList.size());
        expiredKeyList.forEach(k -> {
            //1. 添加到过期数据 Map 中
            List<V> savedList = expiredMap.get(k);
            List<V> newList = this.keyDataMap.get(k);
            if(savedList == null) {
                savedList = new LinkedList<>();
            }
            savedList.addAll(newList);
            expiredMap.put(k, savedList);
            //2. 从存储数据的 Map 中删除
            this.keyDataMap.remove(k);
        });

        return expiredMap;
    }

    /**
     * 获取过期及与之有关联的数据
     * @author zifangsky
     * @date 2021/7/5
     * @since 1.0.0
     * @param associatedKeyFindFunc 用于查找与之关联的Key
     * @return java.util.Map<K,java.util.List<V>>
     */
    public synchronized Map<String, List<V>> getExpiredAndAssociatedData(Function<V, String> associatedKeyFindFunc){
        //1. 查找过期数据
        Map<String, List<V>> expiredMap = this.getExpiredData();

        //2. 查找与之关联 Key 的数据
        Set<String> associatedKeySet = new HashSet<>(expiredMap.keySet());
        Map<String, List<V>> associatedMap = new HashMap<>(16);
        for (List<V> list : expiredMap.values()) {
            for (V item : list) {
                String associatedKey = associatedKeyFindFunc.apply(item);
                if(associatedKey == null) {
                    continue;
                }
                if(StringUtils.isBlank(associatedKey)){
                    continue;
                }
                if(associatedKeySet.contains(associatedKey)) {
                    continue;
                }

                List<V> associatedList = this.get(associatedKey, true);
                if(associatedList != null) {
                    associatedMap.put(associatedKey, associatedList);
                    associatedKeySet.add(associatedKey);
                }
            }
        }

        //3. 添加到一起返回
        expiredMap.putAll(associatedMap);
        return expiredMap;
    }

    /**
     * 遍历所有键值对
     * @param action action
     */
    public void forEach(BiConsumer<? super String, ? super List<V>> action){
        this.keyDataMap.forEach(action);
    }

    /**
     * 清空所有数据
     */
    public synchronized void clear() {
        this.nodeList = new LinkedNodeList();
        this.keyDataMap = new HashMap<>(DEFAULT_INITIAL_CAPACITY);
    }

    /**
     * 将数据添加到Map
     * @param key KEY
     * @param value VALUE
     */
    private void addToDataMap(String key, V value){
        if(value == null) {
            return;
        }

        List<V> list = this.keyDataMap.get(key);
        if(list == null) {
            list = new LinkedList<>();
        }

        list.add(value);
        this.keyDataMap.put(key, list);
    }
}